package com.stark.commons.lang.util;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;

import lombok.Cleanup;

/**
 * 文件操作工具类，对 {@link org.apache.commons.io.FileUtils} 的补充。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0
 */
@SuppressWarnings("unused")
public class FileUtils extends org.apache.commons.io.FileUtils {
	
	/**
	 * 下载文件。
	 * @param request HTTP 请求对象。
	 * @param response HTTP 响应对象。
	 * @param bytes 文件字节码。
	 * @param filename 文件名。
	 * @param charsetName 字符编码。
	 * @throws IOException
	 */
	public static void download(HttpServletRequest request, HttpServletResponse response, byte[] bytes, String filename, String charsetName)
			throws IOException {
		download(request, response, bytes, filename, charsetName, null);
	}
	
	/**
	 * 下载文件。
	 * @param request HTTP 请求对象。
	 * @param response HTTP 响应对象。
	 * @param bytes 文件字节码。
	 * @param filename 文件名。
	 * @param charsetName 字符编码。
	 * @param contentType 媒体类型。
	 * @throws IOException
	 */
	public static void download(HttpServletRequest request, HttpServletResponse response, byte[] bytes, String filename, String charsetName, String contentType)
			throws IOException {
		if (StringUtils.isBlank(filename)) {
			filename = "file";
		}
		String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
		if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
			filename = URLEncoder.encode(filename, "UTF-8");
		} else {
			filename = new String(filename.getBytes(charsetName), "ISO8859-1");
		}
		
		resetResponse(response);
		if (StringUtils.isNotBlank(contentType)) {
			response.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
		}
		response.setHeader("Content-Disposition", "attachment;filename=" + filename);
		@Cleanup OutputStream out = response.getOutputStream();
		out.write(bytes);
		out.flush();
	}
	
	/**
	 * 浏览器中内嵌显示。
	 * @param request HTTP 请求对象。
	 * @param response HTTP 响应对象。
	 * @param bytes 文件字节码。
	 * @param filename 文件名。
	 * @param charsetName 字符编码。
	 * @throws IOException
	 */
	public static void downloadInline(HttpServletRequest request, HttpServletResponse response, byte[] bytes, String filename, String charsetName)
			throws IOException {
		downloadInline(request, response, bytes, filename, charsetName, null);
	}
	
	/**
	 * 浏览器中内嵌显示。
	 * @param request HTTP 请求对象。
	 * @param response HTTP 响应对象。
	 * @param bytes 文件字节码。
	 * @param filename 文件名。
	 * @param charsetName 字符编码。
	 * @param contentType 媒体类型。
	 * @throws IOException
	 */
	public static void downloadInline(HttpServletRequest request, HttpServletResponse response, byte[] bytes, String filename, String charsetName, String contentType)
			throws IOException {
		if (StringUtils.isBlank(filename)) {
			filename = "file";
		}
		String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
		if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
			filename = URLEncoder.encode(filename, "UTF-8");
		} else {
			filename = new String(filename.getBytes(charsetName), "ISO8859-1");
		}
		
		resetResponse(response);
		if (StringUtils.isNotBlank(contentType)) {
			response.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
		}
		response.setHeader("Content-Disposition", "inline;filename=" + filename);
		@Cleanup OutputStream out = response.getOutputStream();
		out.write(bytes);
		out.flush();
	}
	
	/**
	 * 创建目录。
	 * @param pathname 目录路径。
	 * @return 创建成功时返回文件对象，否则返回 {@literal null} 。
	 */
	public static File createDirectory(String pathname) {
		File dir = new File(pathname);
		if (!dir.exists()) {
			if (!dir.mkdirs()) {
				return null;
			}
		}
		return dir;
	}
	
	/**
	 * 递归删除目录。
	 * @param pathname 目录路径。
	 * @return 是否成功。
	 */
	public static boolean deleteDirectory(String pathname) {
		// 路径不存在
		File file = new File(pathname);
		if (!file.exists()) {
			return true;
		}
		
		// 指定路径是文件
		if (file.isFile()) {
			boolean flag = file.delete();
			return flag;
		}
		
		// 指定路径是目录，递归删除子目录
		File[] files = file.listFiles();
		for (File tmp : files) {
			if (!deleteDirectory(tmp.getAbsolutePath())) {
				return false;
			}
		}
		// 删除根目录
		boolean flag = file.delete();
		return flag;
	}
	
	/**
	 * 创建文件。
	 * @param pathname 文件路径。
	 * @return 创建成功时返回文件对象，否则返回 {@literal null} 。
	 * @throws IOException 创建文件时抛出异常。
	 */
	public static File createFile(String pathname) throws IOException {
		File file = new File(pathname);
		if (!file.getParentFile().exists()) {
			if (!file.getParentFile().mkdirs()) {
				return null;
			}
		}
		if (!file.exists()) {
			if (!file.createNewFile()) {
				return null;
			}
		}
		return file;
	}
	
	/**
	 * 删除文件。
	 * @param pathname 文件路径。
	 */
	public static final boolean deleteFile(String pathname) {
		File file = new File(pathname);
		if (!file.exists()) {
			return true;
		}
		boolean flag = file.delete();
		return flag;
	}
	
	/**
	 * 文件是否可写。
	 * @param pathname 文件路径。
	 * @return 可写时返回 {@literal true} ，否则返回 {@literal false} 。
	 */
	public static boolean canWrite(String pathname) {
		File file = new File(pathname);
		return file.renameTo(file);
	}
	
	/**
	 * 等待被占用的文件至可写。
	 * @param pathname 文件名。
	 * @param millis 被占用时延时毫秒数。
	 */
	public static void waitToWrite(String pathname, long millis) {
		while (!canWrite(pathname)) {
			try {
				Thread.sleep(millis);
			} catch (InterruptedException e) {
				// do nothing
			}
		}
	}
	
	private static void resetResponse(HttpServletResponse response) {
		Map<String, List<String>> headerMap = new HashMap<>();
		response.getHeaderNames().forEach(name -> {
			if (!headerMap.containsKey(name)) {
				headerMap.put(name, new ArrayList<String>());
			}
			
			response.getHeaders(name).forEach(value -> {
				List<String> values = headerMap.get(name);
				if (!values.contains(value)) {
					values.add(value);
				}
			});
		});
		response.reset();
		headerMap.forEach((name, values) -> values.forEach(value -> response.addHeader(name, value)));
	}
	
}